package esms.etonenet.boss1069.web;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

public class SpecificationHelper<T> {

	private String order;
	private String orderName;
	private List<String> orderNames;

	private List<SpecificationCondition> conditions;

	public Specification<T> createSpecification() {

		Specification<T> spec = new Specification<T>() {

			@SuppressWarnings("unchecked")
			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

				List<Predicate> p = new ArrayList<Predicate>();

				// 查询条件
				if (conditions != null) {
					for (SpecificationCondition cd : conditions) {
						String path = cd.getPath();
						String type = cd.getType();
						Serializable value = cd.getValue();

						if (value != null && StringUtils.isNotEmpty(value.toString())) {
							if (value instanceof String)
								value = value.toString();

							@SuppressWarnings("rawtypes")
							Path pathQuery = null;
							for (String name : path.split("-")[0].split("\\.")) {
								if (pathQuery == null)
									pathQuery = root.get(name);
								else
									pathQuery = pathQuery.get(name);
							}

							switch (type) {
							case "like":
								switch (cd.getLikeType()) {
								case "pre":
									value = "%" + value;
									break;
								case "after":
									value = value + "%";
									break;
								case "around":
									value = "%" + value + "%";
									break;
								}

								p.add(cb.like(pathQuery.as(String.class), value.toString()));
								break;
							case "likeIgnoreCase":
								switch (cd.getLikeType()) {
								case "pre":
									value = "%" + value;
									break;
								case "after":
									value = value + "%";
									break;
								case "around":
									value = "%" + value + "%";
									break;
								}

								p.add(cb.like(cb.lower(pathQuery.as(String.class)), value.toString().toLowerCase()));
								break;
							case "eq":
								p.add(cb.equal(pathQuery, value));
								break;
							case "notEq":
								p.add(cb.notEqual(pathQuery, value.toString()));
								break;
							case "between":
								if (cd.getStart() instanceof Date && cd.getEnd() instanceof Date)
									p.add(cb.between(pathQuery, (Date) cd.getStart(), (Date) cd.getEnd()));
								else
									p.add(cb.between(pathQuery, cd.getStart().toString(), cd.getEnd().toString()));
								break;
							case "greater":
								if (value instanceof Date)
									p.add(cb.greaterThan(pathQuery, (Date) value));
								else
									p.add(cb.greaterThan(pathQuery, value.toString()));
								break;
							case "less":
								if (value instanceof Date)
									p.add(cb.lessThan(pathQuery, (Date) value));
								else
									p.add(cb.lessThan(pathQuery, value.toString()));
								break;
							case "greaterOrEqual":
								if (value instanceof Date)
									p.add(cb.greaterThanOrEqualTo(pathQuery, (Date) value));
								else
									p.add(cb.greaterThanOrEqualTo(pathQuery, value.toString()));
								break;
							case "lessOrEqual":
								if (value instanceof Date)
									p.add(cb.lessThanOrEqualTo(pathQuery, (Date) value));
								else
									p.add(cb.lessThanOrEqualTo(pathQuery, value.toString()));
								break;
							case "equalOrLikeIgnoreCase":
								if (value.toString().contains("%"))
									p.add(cb.like(cb.lower(pathQuery.as(String.class)),
											value.toString().toLowerCase()));
								else
									p.add(cb.equal(pathQuery, value));
								break;
							case "equalOrLike":
								if (value.toString().contains("%"))
									p.add(cb.like(pathQuery.as(String.class), value.toString()));
								else
									p.add(cb.equal(pathQuery, value));
								break;
							default:
								break;
							}

						}

					}
				}

				Predicate[] predicates = new Predicate[p.size()];
				p.toArray(predicates);
				query = query.where(predicates);

				// 排序条件
				if (StringUtils.isNotEmpty(orderName) && StringUtils.isNotEmpty(order)) {
					Path<?> pathOther = null;
					for (String name : orderName.split("-")[0].split("\\.")) {
						if (pathOther == null)
							pathOther = root.get(name);
						else
							pathOther = pathOther.get(name);
					}
					switch (order) {
					case "asc":
						query.orderBy(cb.asc(pathOther));
						break;

					case "desc":
						query.orderBy(cb.desc(pathOther));
						break;
					}
				} else if (orderNames != null && !orderNames.isEmpty() && StringUtils.isNotEmpty(order)) {

					switch (order) {
					case "asc":
						List<Order> list = new ArrayList<>();
						for (String orderName : orderNames) {
							if (StringUtils.isEmpty(orderName))
								continue;
							Path<?> pathOther = null;
							for (String name : orderName.split("-")[0].split("\\.")) {
								if (pathOther == null)
									pathOther = root.get(name);
								else
									pathOther = pathOther.get(name);
							}
							list.add(cb.asc(pathOther));
						}
						if (!list.isEmpty())
							query.orderBy(list);
						break;

					case "desc":
						List<Order> list2 = new ArrayList<>();
						for (String orderName : orderNames) {
							if (StringUtils.isEmpty(orderName))
								continue;
							Path<?> pathOther = null;
							for (String name : orderName.split("-")[0].split("\\.")) {
								if (pathOther == null)
									pathOther = root.get(name);
								else
									pathOther = pathOther.get(name);
							}
							list2.add(cb.desc(pathOther));
						}
						if (!list2.isEmpty())
							query.orderBy(list2);
						break;
					}
				}
				return query.getGroupRestriction();

			}
		};

		return spec;
	}

	public SpecificationHelper(List<SpecificationCondition> conditions, String order, String orderName) {
		super();
		this.order = order;
		this.orderName = orderName;
		this.conditions = conditions;
	}

	public SpecificationHelper(List<SpecificationCondition> conditions, String order, List<String> orderNames) {
		super();
		this.order = order;
		this.orderNames = orderNames;
		this.conditions = conditions;
	}

	public SpecificationHelper(List<SpecificationCondition> conditions, BasePageRequestParam prm) {
		super();
		this.order = prm.getSortOrder();
		this.orderName = prm.getSortName();
		this.conditions = conditions;
	}

}
